#include <sys_head.h>

// 默认IP
#define MY_IP "10.0.12.15"


// 定义一个全局变量的数组
typedef struct tpc_Sock_fds
{
    int Sock_fd;
    char name[128];
} tpc_Sock_fds;

#define MAX_LIST 20
#define SHM_ID 4

typedef struct tpc_sql_list
{
    tpc_Sock_fds data[MAX_LIST];
    int len;
} tpc_sql_list;

// 求集合最大值 宏函数
#define MAX_FDS(fd, Fd_Max) fd > Fd_Max ? fd : Fd_Max;

int main(int argc, char const *argv[])
{
    /****************************************** 获取命令行参数 *****************************************/
    // 判断命令行参数
    if (argc < 2)
    {
        printf("参数错误:%s ip port\n", argv[0]);
        return -1;
    }
    // 给定默认ip

    char ip_str[128];

    if (3 != argc)
    {
		// 写入默认IP
        strcpy(ip_str, MY_IP);
    }
    else
    {

        strcpy(ip_str, argv[2]);
    }
    /****************************************** 获取命令行参数 *****************************************/

    /***************************************** TCP服务器初始化 ****************************************/
    //  1、创建套接字
    int Sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == Sock_fd)
    {
        ERRLOG("socket erron");
        return -1;
    }

    //  2、填充服务器网络信息结构体
    struct sockaddr_in Sock_addr;
    Sock_addr.sin_family = AF_INET;                // 选择 IPV4
    Sock_addr.sin_port = htons(atoi(argv[1]));     // 给定端口 // atoi 将字符串 转换成数字
    Sock_addr.sin_addr.s_addr = inet_addr(ip_str); // IP地址

    socklen_t Sock_size = sizeof(Sock_addr);

    //  3、将套接字与服务器的网络信息结构体绑定
    if (-1 == bind(Sock_fd, (struct sockaddr *)&Sock_addr, Sock_size))
    {
        ERRLOG("bind err");
        return -1;
    }

    //  4、将套接字设置城 监听状态

    if (-1 == listen(Sock_fd, 20))
    {
        ERRLOG("Sock_fd err");
        return -1;
    }

    printf("启动服务器\n");

    /***************************************** TCP服务器初始化 ****************************************/

    /***************************************** 初始化IO复用集合 ***************************************/
    // 创建集合
    fd_set Tcp_client_fds;
    fd_set Tcp_client_fds_temp;

    // 创建集合的最大值
    int Max_fd;

    // 清空母本
    FD_ZERO(&Tcp_client_fds);

    // 将监听的套接字给集合进行检测
    FD_SET(Sock_fd, &Tcp_client_fds);
    Max_fd = MAX_FDS(Sock_fd, Max_fd);

    // 就绪描述符个数
    int fds_num;

    /***************************************** 初始化IO复用集合 ***************************************/

    /***************************************** 初始化顺序表 ***************************************/

    tpc_sql_list list;

    list.len = 0;

    /***************************************** 初始化顺序表 ***************************************/

    /***************************************** 初始化缓冲区 ***************************************/

    char buf[1024];
    char buf_send[2048];
    char buf_recv[1024];

    memset(buf, 0, sizeof(buf));
    memset(buf_send, 0, sizeof(buf_send));
    memset(buf_recv, 0, sizeof(buf_recv));

    /***************************************** 初始化缓冲区 ***************************************/

    while (1)
    {

        memset(buf, 0, sizeof(buf));
        memset(buf_send, 0, sizeof(buf_send));
        memset(buf_recv, 0, sizeof(buf_recv));

        // 副本获取母本数据
        Tcp_client_fds_temp = Tcp_client_fds;
        // 开始检测
        fds_num = select(Max_fd + 1, &Tcp_client_fds_temp, NULL, NULL, NULL);

        /*
            说明: select 现在检测的是 服务器的TCP 套接字 和 客户端的TCP套接字
            当 服务器的TCP 套接字 Sock_fd 就绪时 就代表有新的用户产生了链接
            当 客户端的TCP 套接字 acc_fd 就绪时 就代表有用户发送了信息
        */

        // 服务器的 TCP 套接字 Sock_fd 就绪
        if (FD_ISSET(Sock_fd, &Tcp_client_fds_temp))
        {
            //  5、阻塞等待客户端连接 accept
            int acc_fd = accept(Sock_fd, NULL, NULL);
            if (-1 == acc_fd)
            {
                ERRLOG("accept err");
                return -1;
            }

            // 检测是否超过最大连接数
            if (list.len >= MAX_LIST)
            {
                printf("已经满了\n");
                send(acc_fd, "已经满了", strlen("已经满了"), 0);
                continue;
            }

            // 更新集合
            FD_SET(acc_fd, &Tcp_client_fds);
            Max_fd = MAX_FDS(acc_fd, Max_fd);

            // 更新顺序表
            list.data[list.len].Sock_fd = acc_fd;

            sprintf(list.data[list.len].name, "新用户_00%d", list.len);

            sprintf(buf_send, "连接成功 : %s", list.data[list.len].name);

            send(acc_fd, buf_send, strlen(buf_send), 0);

            printf("name [%s] fd = [%d]\n" , list.data[list.len].name , list.data[list.len].Sock_fd);

            list.len++;
        }

        // 非连接 通讯
        for (size_t i = 0; i < Max_fd + 1; i++)
        {
            // 判断那个文件描述符就绪
            if (i != Sock_fd && FD_ISSET(i, &Tcp_client_fds_temp))
            {
                int index;
                // 求当前的文件描述符 在 顺序表中的位置
                for (size_t j = 0; j < MAX_LIST ; j++)
                {
                    if (list.data[j].Sock_fd == i)
                    {
                        index = j;
                        break;
                    }
                }

                // 接收字符串
                int recv_len = recv(list.data[index].Sock_fd, buf_recv, sizeof(buf_recv), 0);

                // 客户端关闭了连接
                if (0 == recv_len)
                {
                    // 关闭了连接
                    printf("客户关闭了连接....\n");

                    // 从集合删除
                    FD_CLR(i, &Tcp_client_fds_temp);

                    close(i);

                    // 从顺序表删除

                    for (size_t j = index; j < MAX_LIST; j++)
                    {
                        list.data[j] = list.data[j];
                    }

                    list.len--;

                    continue;
                }
                // 客户端操作

                // 修改名字
                if (NULL != strstr(buf_recv, "set_name_"))
                {
                    // 写入姓名
                    strcpy(list.data[index].name , buf_recv + 9);
                    // printf("改名字：%s index%d fd %d\n" , list.data[index].name , index , list.data[index].Sock_fd);
                    sprintf(buf, "修改名称成功:%s", list.data[index].name);
                    send( list.data[index].Sock_fd , buf, strlen(buf), 0);
                    continue;
                }
                // 转发 群发

                // 发送给其他成员

                // 组装字符串
                sprintf(buf_send, "%s : %s", list.data[index].name, buf_recv);
                
                for (size_t j = 0; j < list.len ; j++)
                {
                    if (j != index)
                    {
                        printf("fd = [%d] name = [%s]\n", list.data[j].Sock_fd , list.data[j].name);
                        send(list.data[j].Sock_fd, buf_send , strlen(buf_send), 0);
                    }
                }
            }
        }
    }

    //  7、关闭套接字  close
    close(Sock_fd);
    return 0;
}